namespace Frida.Fruity.Injector {
	public static async GadgetDetails inject (owned Gum.DarwinModule module, LLDB.Client lldb, ChannelProvider channel_provider,
			Cancellable? cancellable) throws Error, IOError {
		var session = new Session (module, lldb, channel_provider);
		return yield session.run (cancellable);
	}

	public class GadgetDetails : Object {
		public uint16 port {
			get;
			construct;
		}

		public GadgetDetails (uint16 port) {
			Object (port: port);
		}
	}

	public errordomain Error {
		UNSUPPORTED
	}

	private class Session : Object {
		public Gum.DarwinModule module {
			get;
			construct;
		}

		public LLDB.Client lldb {
			get;
			construct;
		}

		public ChannelProvider channel_provider {
			get;
			construct;
		}

		public Gum.CpuType cpu_type {
			get;
			construct;
			default = ARM64;
		}

		public uint page_size {
			get;
			construct;
			default = 16384;
		}

		public uint pointer_size {
			get;
			construct;
			default = 8;
		}

		private LLDB.Thread? main_thread;
		private LLDB.Thread.Snapshot? saved_state;
		private LLDB.Thread.StackBounds? stack_bounds;

		private uint64 jit_page;
		private uint64 scratch_page;

		private LLDB.AppleDyldFields? dyld_fields;
		private uint64 dyld_base;
		private Gee.HashMap<string, uint64?>? dyld_symbols;

		private bool libsystem_initialized;

		private size_t module_size;

		public Session (Gum.DarwinModule module, LLDB.Client lldb, ChannelProvider channel_provider) {
			Object (
				module: module,
				lldb: lldb,
				channel_provider: channel_provider
			);
		}

		public async GadgetDetails run (Cancellable? cancellable) throws Error, IOError {
			try {
				var existing_gadget = yield setup (cancellable);
				if (existing_gadget != null) {
					yield lldb.detach (cancellable);
					return existing_gadget;
				}

				bool is_early_instrumentation = !libsystem_initialized;

				yield ensure_libsystem_initialized (cancellable);
				var result = yield inject_module (cancellable);

				yield teardown (cancellable);

				if (is_early_instrumentation) {
					var gadget_threads = new Gee.ArrayList<LLDB.Thread> ();
					yield lldb.enumerate_threads (thread => {
						if (thread.id != main_thread.id)
							gadget_threads.add (thread);
						return true;
					}, cancellable);
					yield lldb.continue_specific_threads (gadget_threads, cancellable);
				} else {
					yield lldb.detach (cancellable);
				}

				return result;
			} catch (GLib.Error e) {
				throw new Error.UNSUPPORTED ("%s", e.message);
			}
		}

		private async GadgetDetails? setup (Cancellable? cancellable) throws GLib.Error {
			GadgetDetails? existing_gadget = null;
			yield lldb.enumerate_threads (thread => {
				if (main_thread == null)
					main_thread = thread;

				unowned string? name = thread.name;
				if (name == null)
					return true;

				MatchInfo info;
				if (/^frida-gadget-tcp-(\d+)$/.match (name, 0, out info)) {
					string raw_port = info.fetch (1);

					try {
						uint64 port;
						const uint radix = 10;
						uint64.from_string (raw_port, out port, radix, 1, uint16.MAX);

						existing_gadget = new GadgetDetails ((uint16) port);

						return false;
					} catch (NumberParserError e) {
					}
				}

				return true;
			}, cancellable);
			if (existing_gadget != null)
				return existing_gadget;
			yield save_main_thread_state (cancellable);

			jit_page = yield lldb.allocate (page_size, "rx", cancellable);
			scratch_page = yield lldb.allocate (page_size, "rw", cancellable);

			dyld_fields = yield lldb.get_apple_dyld_fields (cancellable);
			libsystem_initialized = yield lldb.read_bool (dyld_fields.libsystem_initialized, cancellable);
			if (libsystem_initialized) {
				dyld_base = yield lldb.read_pointer (dyld_fields.dyld_load_address, cancellable);
			} else {
				yield lldb.enumerate_modules (m => {
					if (m.pathname == "/usr/lib/dyld") {
						dyld_base = m.load_address;
						return false;
					}

					return true;
				}, cancellable);
			}
			dyld_symbols = yield fetch_dyld_symbols (cancellable);

			return null;
		}

		private async void teardown (Cancellable? cancellable) throws GLib.Error {
			yield lldb.deallocate (scratch_page, cancellable);
			yield lldb.deallocate (jit_page, cancellable);

			yield restore_main_thread_state (cancellable);
		}

		private async void save_main_thread_state (Cancellable? cancellable) throws GLib.Error {
			assert (saved_state == null);
			saved_state = yield main_thread.save_register_state (cancellable);

			stack_bounds = null;
		}

		private async void restore_main_thread_state (Cancellable? cancellable) throws GLib.Error {
			assert (saved_state != null);
			yield main_thread.restore_register_state (saved_state, cancellable);
			saved_state = null;
		}

		private async GadgetDetails inject_module (Cancellable? cancellable) throws GLib.Error {
			module_size = compute_virtual_size (module);
			module.base_address = yield lldb.allocate (module_size, "rw", cancellable);

			unowned Gum.DarwinModuleImage image = module.image;
			unowned uint8[] image_data = ((uint8[]) image.data)[0:image.size];
			var buffer = lldb.make_buffer (new Bytes.static (image_data));

			perform_rebase_operations (buffer);

			var symbols_needed = new SymbolQueryBuilder ();
			var threaded_items = new ThreadedItemsBuilder ();
			var chained_fixups = new ChainedFixupsBuilder ();

			collect_needed_symbols_and_threaded_items (buffer, symbols_needed, threaded_items);
			collect_chained_fixups (chained_fixups);
			UploadSymbols.extend_query (symbols_needed);

			var symbols = yield resolve_symbols (symbols_needed.build (), cancellable);

			if (threaded_items.is_empty)
				perform_bind_operations (buffer, symbols);

			var gadget_port = yield upload (buffer, threaded_items.build (symbols), chained_fixups.build (),
				new UploadSymbols.from_set (symbols), cancellable);

			return new GadgetDetails (gadget_port);
		}

		private void perform_rebase_operations (LLDB.Buffer buffer) throws GLib.Error {
			GLib.Error? pending_error = null;

			module.enumerate_rebases (rebase => {
				switch (rebase.type) {
					case POINTER:
					case TEXT_ABSOLUTE32:
						break;
					default:
						pending_error = new Error.UNSUPPORTED ("Unsupported rebase type: %u", rebase.type);
						return false;
				}

				size_t offset = (size_t) (rebase.segment.file_offset + rebase.offset);
				uint64 address = buffer.read_pointer (offset);
				buffer.write_pointer (offset, address + rebase.slide);

				return true;
			});

			if (pending_error != null)
				throw pending_error;
		}

		private void collect_needed_symbols_and_threaded_items (LLDB.Buffer buffer, SymbolQueryBuilder symbols_needed,
				ThreadedItemsBuilder threaded_items) throws GLib.Error {
			Gum.Address slide = module.slide;
			bool have_threaded_items = false;
			GLib.Error? pending_error = null;

			Gum.FoundDarwinBindFunc collect_bind = bind => {
				switch (bind.type) {
					case POINTER: {
						switch (bind.library_ordinal) {
							case SELF:
							case MAIN_EXECUTABLE:
							case FLAT_LOOKUP:
							case WEAK_LOOKUP:
								pending_error = new Error.UNSUPPORTED ("Unsupported bind ordinal: %d",
									bind.library_ordinal);
								return false;
							default:
								break;
						}

						unowned string module_name = module.get_dependency_by_ordinal (bind.library_ordinal);
						unowned string symbol_name = bind.symbol_name;

						symbols_needed.add (module_name, symbol_name);
						if (have_threaded_items)
							threaded_items.add_symbol (module_name, symbol_name);

						break;
					}
					case THREADED_TABLE:
						have_threaded_items = true;
						break;
					case THREADED_ITEMS:
						threaded_items.add_region (bind.segment.vm_address + slide + bind.offset);
						break;
					default:
						pending_error = new Error.UNSUPPORTED ("Unsupported bind type: %u", bind.type);
						return false;
				}

				return true;
			};

			module.enumerate_binds (collect_bind);
			if (pending_error != null)
				throw pending_error;

			module.enumerate_lazy_binds (collect_bind);
			if (pending_error != null)
				throw pending_error;
		}

		private void collect_chained_fixups (ChainedFixupsBuilder chained_fixups) {
			module.enumerate_chained_fixups (fixup => {
				chained_fixups.add_location (fixup.vm_address);
				return true;
			});
		}

		private void perform_bind_operations (LLDB.Buffer buffer, SymbolSet symbols) throws GLib.Error {
			GLib.Error? pending_error = null;

			Gum.FoundDarwinBindFunc perform_bind = bind => {
				unowned string module_name = module.get_dependency_by_ordinal (bind.library_ordinal);
				unowned string symbol_name = bind.symbol_name;

				uint64 address;
				if (!symbols.lookup (module_name, symbol_name, out address)) {
					bool is_weak = (bind.symbol_flags & Gum.DarwinBindSymbolFlags.WEAK_IMPORT) != 0;
					if (is_weak || is_dyld_stub_binder (module_name, symbol_name))
						return true;
					pending_error = new Error.UNSUPPORTED ("Unable to resolve symbol: %s", bind.symbol_name);
					return false;
				}

				buffer.write_pointer ((size_t) (bind.segment.file_offset + bind.offset), address + bind.addend);

				return true;
			};

			module.enumerate_binds (perform_bind);
			if (pending_error != null)
				throw pending_error;

			module.enumerate_lazy_binds (perform_bind);
			if (pending_error != null)
				throw pending_error;
		}

		private async uint16 upload (LLDB.Buffer buffer, ThreadedItems threaded_items, ChainedFixups chained_fixups,
				UploadSymbols symbols, Cancellable? cancellable) throws GLib.Error {
			uint64 code = jit_page;
			yield lldb.write_byte_array (code, new Bytes.static (UPLOAD_LISTENER_CODE), cancellable);

			const uint64 rx_buffer_size = 1024 * 1024;

			uint64 args = scratch_page;
			var args_builder = lldb.make_buffer_builder ();

			string range_param = ("frida_dylib_range=0x%" + uint64.FORMAT_MODIFIER + "x,0x%" + size_t.FORMAT_MODIFIER + "x")
				.printf (module.base_address, module_size);

			var config = new Json.Builder ();
			config
				.begin_object ()
					.set_member_name ("interaction")
					.begin_object ()
						.set_member_name ("type")
						.add_string_value ("listen")
						.set_member_name ("port")
						.add_int_value (27043)
						.set_member_name ("on_port_conflict")
						.add_string_value ("pick-next")
						.set_member_name ("on_load")
						.add_string_value ("resume")
					.end_object ()
					.set_member_name ("teardown")
					.add_string_value ("full")
				.end_object ();
			string raw_config = Json.to_string (config.get_root (), false);
			string config_param = "frida_gadget_config=" + Base64.encode (raw_config.data);

			var apple_strv_builder = new StringVectorBuilder (args_builder);

			apple_strv_builder
				.append_string (range_param)
				.append_string (config_param)
				.append_terminator ();
			var apple_strv_offset = apple_strv_builder.append_placeholder ();

			var api_offset = args_builder.offset;
			args_builder
				.append_pointer (symbols.socket)
				.append_pointer (symbols.setsockopt)
				.append_pointer (symbols.bind)
				.append_pointer (symbols.listen)
				.append_pointer (symbols.getsockname)
				.append_pointer (symbols.accept)
				.append_pointer (symbols.read)
				.append_pointer (symbols.sys_icache_invalidate)
				.append_pointer (symbols.sys_dcache_flush)
				.append_pointer (symbols._mach_task_self)
				.append_pointer (symbols.mach_vm_allocate)
				.append_pointer (symbols.mach_vm_deallocate)
				.append_pointer (symbols.dlopen)
				.append_pointer (symbols.dlsym)
				.append_pointer (symbols.mprotect)
				.append_pointer (symbols.close)
				.append_pointer (symbols.get_errno_storage);

			apple_strv_builder.build (args);

			yield lldb.write_byte_array (args, args_builder.build (), cancellable);

			uint64 apple_strv = args + apple_strv_offset;
			uint64 upload_api = args + api_offset;

			uint64 listen_result = yield invoke_remote_function (code, {
					rx_buffer_size,
					upload_api
				}, null, cancellable);

			uint8  error_code    =  (uint8) ((listen_result >> 56) & 0xff);
			uint32 listener_fd   = (uint32) ((listen_result >> 16) & 0xffffffffU);
			uint16 listener_port = (uint16)  (listen_result        & 0xffff);

			if (error_code != 0)
				throw new Error.UNSUPPORTED ("Unable to listen on TCP (error_code=%u)", error_code);

			yield lldb.write_byte_array (code, new Bytes.static (UPLOAD_RECEIVER_CODE), cancellable);

			// The generated session ID only acts as a sanitycheck for now
			var rand = new Rand.with_seed ((uint32) dyld_base);
			uint64 session_id_top    = ((uint64) rand.next_int () << 32) | ((uint64) rand.next_int ());
			uint64 session_id_bottom = ((uint64) rand.next_int () << 32) | ((uint64) rand.next_int ());

			perform_upload.begin (buffer, threaded_items, chained_fixups, listener_port, session_id_top, session_id_bottom,
				symbols, cancellable);

			int64 receive_result = (int64) yield invoke_remote_function (code, {
					listener_fd,
					session_id_top,
					session_id_bottom,
					apple_strv,
					upload_api
				}, null, cancellable);
			if (receive_result <= 0)
				throw new Error.UNSUPPORTED ("Unable to start gadget: %" + int64.FORMAT_MODIFIER + "d", receive_result);

			return (uint16) receive_result;
		}

		private async void perform_upload (LLDB.Buffer buffer, ThreadedItems threaded_items, ChainedFixups chained_fixups,
				uint16 listener_port, uint64 session_id_top, uint64 session_id_bottom, UploadSymbols symbols,
				Cancellable? cancellable) {
			try {
				var stream = yield channel_provider.open_channel (
					("tcp:%" + uint16.FORMAT_MODIFIER + "u").printf (listener_port),
					cancellable);
				var output = stream.output_stream;
				var io_priority = Priority.DEFAULT;

				var hello = lldb.make_buffer_builder ()
					.append_uint64 (session_id_top)
					.append_uint64 (session_id_bottom)
					.build ();
				size_t bytes_written;
				yield output.write_all_async (hello.get_data (), io_priority, cancellable, out bytes_written);

				uint64 slide = module.slide;

				foreach (unowned Gum.DarwinSegment segment in module.segments.data) {
					uint64 address = segment.vm_address + slide;

					Bytes bytes = new Bytes.from_bytes (buffer.bytes, (size_t) segment.file_offset,
						(size_t) segment.file_size);

					var write_command_header = lldb.make_buffer_builder ()
						.append_uint8 (UploadCommandType.WRITE)
						.append_uint64 (address)
						.append_uint32 ((uint32) bytes.get_size ())
						.build ();
					yield output.write_all_async (write_command_header.get_data (), io_priority, cancellable,
						out bytes_written);
					yield output.write_all_async (bytes.get_data (), io_priority, cancellable, out bytes_written);
				}

				if (!threaded_items.is_empty) {
					var command = lldb.make_buffer_builder ()
						.append_uint8 (UploadCommandType.APPLY_THREADED)
						.append_uint64 (module.preferred_address)
						.append_uint64 (slide);

					var symbol_addrs = threaded_items.symbol_addrs;
					command.append_uint16 ((uint16) symbol_addrs.size);
					foreach (var symbol in symbol_addrs)
						command.append_uint64 (symbol);

					var region_bases = threaded_items.region_bases;
					command.append_uint16 ((uint16) region_bases.size);
					foreach (var region in region_bases)
						command.append_uint64 (region);

					yield output.write_all_async (command.build ().get_data (), io_priority, cancellable,
						out bytes_written);
				}

				foreach (uint64 fixups_header_address in chained_fixups.locations) {
					var fixup_command = lldb.make_buffer_builder ()
						.append_uint8 (UploadCommandType.PROCESS_FIXUPS)
						.append_uint64 (fixups_header_address)
						.append_uint64 (module.base_address)
						.append_uint64 (module.preferred_address)
						.build ();
					yield output.write_all_async (fixup_command.get_data (), io_priority, cancellable,
						out bytes_written);
				}

				foreach (unowned Gum.DarwinSegment segment in module.segments.data) {
					uint64 address = segment.vm_address + slide;

					var protect_command = lldb.make_buffer_builder ()
						.append_uint8 (UploadCommandType.PROTECT)
						.append_uint64 (address)
						.append_uint32 ((uint32) segment.vm_size)
						.append_uint32 ((uint32) segment.protection)
						.build ();
					yield output.write_all_async (protect_command.get_data (), io_priority, cancellable,
						out bytes_written);
				}

				var construct_commands = lldb.make_buffer_builder ();
				module.enumerate_init_pointers (ptrs => {
					construct_commands
						.append_uint8 (UploadCommandType.CONSTRUCT_FROM_POINTERS)
						.append_uint64 (ptrs.address)
						.append_uint32 ((uint32) ptrs.count);
					return true;
				});
				module.enumerate_init_offsets (offsets => {
					construct_commands
						.append_uint8 (UploadCommandType.CONSTRUCT_FROM_OFFSETS)
						.append_uint64 (offsets.address)
						.append_uint32 ((uint32) offsets.count)
						.append_uint64 (module.base_address);
					return true;
				});
				yield output.write_all_async (construct_commands.build ().get_data (), io_priority, cancellable,
					out bytes_written);

				yield stream.close_async (io_priority, cancellable);
			} catch (GLib.Error e) {
			}
		}

		private enum UploadCommandType {
			WRITE = 1,
			APPLY_THREADED,
			PROCESS_FIXUPS,
			PROTECT,
			CONSTRUCT_FROM_POINTERS,
			CONSTRUCT_FROM_OFFSETS,
		}

		private class UploadSymbols {
			public uint64 socket;
			public uint64 setsockopt;
			public uint64 bind;
			public uint64 listen;
			public uint64 getsockname;
			public uint64 accept;
			public uint64 read;
			public uint64 sys_icache_invalidate;
			public uint64 sys_dcache_flush;
			public uint64 _mach_task_self;
			public uint64 mach_vm_allocate;
			public uint64 mach_vm_deallocate;
			public uint64 dlopen;
			public uint64 dlsym;
			public uint64 mprotect;
			public uint64 close;
			public uint64 get_errno_storage;

			private const string LIBSYSTEM = "/usr/lib/libSystem.B.dylib";

			public static void extend_query (SymbolQueryBuilder builder) {
				builder
					.add (LIBSYSTEM, "_socket")
					.add (LIBSYSTEM, "_setsockopt")
					.add (LIBSYSTEM, "_bind")
					.add (LIBSYSTEM, "_listen")
					.add (LIBSYSTEM, "_getsockname")
					.add (LIBSYSTEM, "_accept")
					.add (LIBSYSTEM, "_read")
					.add (LIBSYSTEM, "_sys_icache_invalidate")
					.add (LIBSYSTEM, "_sys_dcache_flush")
					.add (LIBSYSTEM, "_mach_task_self")
					.add (LIBSYSTEM, "_mach_vm_allocate")
					.add (LIBSYSTEM, "_mach_vm_deallocate")
					.add (LIBSYSTEM, "_dlopen")
					.add (LIBSYSTEM, "_dlsym")
					.add (LIBSYSTEM, "_mprotect")
					.add (LIBSYSTEM, "_close")
					.add (LIBSYSTEM, "__error")
					;
			}

			public UploadSymbols.from_set (SymbolSet symbols) throws Error {
				socket                = symbols.get (LIBSYSTEM, "_socket");
				setsockopt            = symbols.get (LIBSYSTEM, "_setsockopt");
				bind                  = symbols.get (LIBSYSTEM, "_bind");
				listen                = symbols.get (LIBSYSTEM, "_listen");
				getsockname           = symbols.get (LIBSYSTEM, "_getsockname");
				accept                = symbols.get (LIBSYSTEM, "_accept");
				read                  = symbols.get (LIBSYSTEM, "_read");
				sys_icache_invalidate = symbols.get (LIBSYSTEM, "_sys_icache_invalidate");
				sys_dcache_flush      = symbols.get (LIBSYSTEM, "_sys_dcache_flush");
				_mach_task_self       = symbols.get (LIBSYSTEM, "_mach_task_self");
				mach_vm_allocate      = symbols.get (LIBSYSTEM, "_mach_vm_allocate");
				mach_vm_deallocate    = symbols.get (LIBSYSTEM, "_mach_vm_deallocate");
				dlopen                = symbols.get (LIBSYSTEM, "_dlopen");
				dlsym                 = symbols.get (LIBSYSTEM, "_dlsym");
				mprotect              = symbols.get (LIBSYSTEM, "_mprotect");
				close                 = symbols.get (LIBSYSTEM, "_close");
				get_errno_storage     = symbols.get (LIBSYSTEM, "__error");
			}
		}

		/* Compiled from helpers/upload-listener.c */
		private const uint8[] UPLOAD_LISTENER_CODE = {
			0xff, 0x43, 0x01, 0xd1, 0xf6, 0x57, 0x02, 0xa9, 0xf4, 0x4f, 0x03, 0xa9, 0xfd, 0x7b, 0x04, 0xa9, 0xfd, 0x03, 0x01,
			0x91, 0xf3, 0x03, 0x01, 0xaa, 0xe0, 0x1f, 0x00, 0xb9, 0x28, 0x00, 0x40, 0xf9, 0x40, 0x00, 0x80, 0x52, 0x21, 0x00,
			0x80, 0x52, 0x02, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0x80, 0x05, 0x00, 0x54, 0xf4,
			0x03, 0x00, 0xaa, 0x68, 0x06, 0x40, 0xf9, 0xe3, 0x73, 0x00, 0x91, 0xe1, 0xff, 0x9f, 0x52, 0x42, 0x00, 0x82, 0x52,
			0x84, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0xa0, 0x04, 0x00, 0x54, 0x48, 0x00, 0x80,
			0x52, 0xe8, 0x27, 0x00, 0x39, 0xe8, 0x0f, 0x80, 0x52, 0x08, 0x20, 0xa0, 0x72, 0xe8, 0x0f, 0x00, 0xb9, 0xff, 0x17,
			0x00, 0x79, 0x08, 0x02, 0x80, 0x52, 0xe8, 0x07, 0x00, 0xb9, 0x68, 0x0a, 0x40, 0xf9, 0xe1, 0x23, 0x00, 0x91, 0xe0,
			0x03, 0x14, 0xaa, 0x02, 0x02, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0x00, 0x03, 0x00, 0x54,
			0x68, 0x12, 0x40, 0xf9, 0xe1, 0x23, 0x00, 0x91, 0xe2, 0x13, 0x00, 0x91, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f,
			0xd6, 0x1f, 0x04, 0x00, 0x31, 0x60, 0x02, 0x00, 0x54, 0x68, 0x0e, 0x40, 0xf9, 0xe0, 0x03, 0x14, 0xaa, 0x21, 0x00,
			0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0xe0, 0x01, 0x00, 0x54, 0x88, 0x7e, 0x40, 0x93, 0xe9,
			0x17, 0x40, 0x79, 0x29, 0x09, 0xc0, 0x5a, 0x35, 0x7d, 0x10, 0x53, 0x15, 0xbd, 0x70, 0xb3, 0x0d, 0x00, 0x00, 0x14,
			0x15, 0x20, 0xe0, 0xd2, 0x0b, 0x00, 0x00, 0x14, 0x15, 0x40, 0xe0, 0xd2, 0x06, 0x00, 0x00, 0x14, 0x15, 0x60, 0xe0,
			0xd2, 0x04, 0x00, 0x00, 0x14, 0x15, 0x80, 0xe0, 0xd2, 0x02, 0x00, 0x00, 0x14, 0x15, 0xa0, 0xe0, 0xd2, 0x68, 0x3e,
			0x40, 0xf9, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0xe0, 0x03, 0x15, 0xaa, 0xfd, 0x7b, 0x44, 0xa9, 0xf4,
			0x4f, 0x43, 0xa9, 0xf6, 0x57, 0x42, 0xa9, 0xff, 0x43, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6
		};

		/* Compiled from helpers/upload-receiver.c */
		private const uint8[] UPLOAD_RECEIVER_CODE = {
			0xfc, 0x6f, 0xba, 0xa9, 0xfa, 0x67, 0x01, 0xa9, 0xf8, 0x5f, 0x02, 0xa9, 0xf6, 0x57, 0x03, 0xa9, 0xf4, 0x4f, 0x04,
			0xa9, 0xfd, 0x7b, 0x05, 0xa9, 0xfd, 0x43, 0x01, 0x91, 0xff, 0xc3, 0x02, 0xd1, 0xf3, 0x03, 0x04, 0xaa, 0xf9, 0x03,
			0x03, 0xaa, 0xf7, 0x03, 0x02, 0xaa, 0xf8, 0x03, 0x01, 0xaa, 0xf4, 0x03, 0x00, 0xaa, 0xbf, 0xc3, 0x19, 0xb8, 0x15,
			0x02, 0x80, 0x52, 0xb5, 0x43, 0x18, 0xb8, 0x68, 0x16, 0x40, 0xf9, 0xa1, 0xe3, 0x01, 0xd1, 0xa2, 0xf3, 0x01, 0xd1,
			0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0xe1, 0x00, 0x00, 0x54, 0x68, 0x42, 0x40,
			0xf9, 0x00, 0x01, 0x3f, 0xd6, 0x08, 0x00, 0x40, 0xb9, 0x1f, 0x11, 0x00, 0x71, 0xa0, 0xfe, 0xff, 0x54, 0xb5, 0x01,
			0x00, 0x14, 0xf6, 0x03, 0x00, 0xaa, 0xa1, 0x43, 0x02, 0xd1, 0x02, 0x02, 0x80, 0x52, 0x1d, 0x02, 0x00, 0x94, 0xe0,
			0x00, 0x00, 0x34, 0xa8, 0x03, 0x57, 0xf8, 0x1f, 0x01, 0x18, 0xeb, 0x81, 0x00, 0x00, 0x54, 0xa8, 0x83, 0x57, 0xf8,
			0x1f, 0x01, 0x17, 0xeb, 0x60, 0x00, 0x00, 0x54, 0x27, 0x02, 0x00, 0x94, 0xe6, 0xff, 0xff, 0x17, 0x3b, 0x00, 0x80,
			0x52, 0x1c, 0xfd, 0x9f, 0x52, 0xfc, 0xff, 0xaf, 0x72, 0xa1, 0x47, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x22, 0x00,
			0x80, 0x52, 0x0d, 0x02, 0x00, 0x94, 0xe0, 0x33, 0x00, 0x34, 0xa8, 0xf3, 0x56, 0x38, 0x10, 0x05, 0x00, 0x51, 0x1f,
			0x16, 0x00, 0x71, 0x68, 0x33, 0x00, 0x54, 0x1f, 0x16, 0x00, 0xf1, 0x10, 0x92, 0x9f, 0x9a, 0xd1, 0x34, 0x00, 0x10,
			0x1f, 0x20, 0x03, 0xd5, 0x30, 0x7a, 0xb0, 0xb8, 0x30, 0x02, 0x10, 0x8b, 0x00, 0x02, 0x1f, 0xd6, 0xa1, 0x83, 0x01,
			0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0xfd, 0x01, 0x00, 0x94, 0x60, 0x1e, 0x00, 0x34, 0xa1, 0xa3,
			0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x82, 0x00, 0x80, 0x52, 0xf8, 0x01, 0x00, 0x94, 0xc0, 0x1d, 0x00, 0x34, 0xa1,
			0x03, 0x5a, 0xf8, 0xa2, 0x83, 0x55, 0xb8, 0xa3, 0x83, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0xe4, 0x03, 0x13, 0xaa,
			0x9c, 0x01, 0x00, 0x94, 0xf7, 0x03, 0x00, 0xaa, 0x68, 0x1e, 0x40, 0xf9, 0x03, 0x02, 0x00, 0x94, 0x68, 0x22, 0x40,
			0xf9, 0x01, 0x02, 0x00, 0x94, 0x48, 0x01, 0x80, 0x52, 0xe3, 0x00, 0x00, 0x14, 0xa1, 0x83, 0x02, 0xd1, 0xe0, 0x03,
			0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0xe6, 0x01, 0x00, 0x94, 0x20, 0x1b, 0x00, 0x34, 0xa1, 0xa3, 0x02, 0xd1, 0xe0,
			0x03, 0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0xe1, 0x01, 0x00, 0x94, 0x80, 0x1a, 0x00, 0x34, 0xa1, 0xc3, 0x02, 0xd1,
			0xe0, 0x03, 0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0xdc, 0x01, 0x00, 0x94, 0xe0, 0x19, 0x00, 0x34, 0xb9, 0x03, 0x11,
			0xf8, 0xb7, 0xa3, 0x75, 0xa9, 0xa8, 0x03, 0x14, 0xf8, 0xa8, 0x03, 0x55, 0xf8, 0xa8, 0x83, 0x13, 0xf8, 0x68, 0x26,
			0x40, 0xf9, 0x00, 0x01, 0x3f, 0xd6, 0xbf, 0x03, 0x1a, 0xf8, 0x68, 0x2a, 0x40, 0xf9, 0xa1, 0x83, 0x01, 0xd1, 0xa0,
			0xc3, 0x10, 0xb8, 0x22, 0x00, 0xa0, 0x52, 0x23, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x19, 0x00, 0x80, 0x52,
			0x15, 0x00, 0x80, 0xd2, 0xb8, 0x03, 0x5a, 0xf8, 0xfa, 0x82, 0x00, 0x91, 0xe8, 0x12, 0x40, 0xb9, 0x3f, 0x03, 0x08,
			0x6b, 0xe0, 0x18, 0x00, 0x54, 0x48, 0x03, 0x40, 0xb9, 0x09, 0x01, 0x1c, 0x0b, 0x3f, 0x2d, 0x00, 0x71, 0x69, 0x23,
			0xc9, 0x1a, 0x2a, 0x10, 0x81, 0x52, 0x29, 0x01, 0x0a, 0x0a, 0x24, 0x99, 0x40, 0x7a, 0x04, 0x09, 0x4c, 0x7a, 0x01,
			0x01, 0x00, 0x54, 0x48, 0x0b, 0x40, 0xb9, 0x40, 0x03, 0x08, 0x8b, 0x68, 0x32, 0x40, 0xf9, 0x21, 0x01, 0x80, 0x52,
			0x00, 0x01, 0x3f, 0xd6, 0x00, 0x7b, 0x35, 0xf8, 0xb5, 0x06, 0x00, 0x91, 0x48, 0x07, 0x40, 0xb9, 0x5a, 0x03, 0x08,
			0x8b, 0x39, 0x07, 0x00, 0x11, 0xea, 0xff, 0xff, 0x17, 0xa1, 0x83, 0x01, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x02, 0x01,
			0x80, 0x52, 0xae, 0x01, 0x00, 0x94, 0x00, 0x10, 0x00, 0x34, 0xa1, 0x83, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x82,
			0x00, 0x80, 0x52, 0xa9, 0x01, 0x00, 0x94, 0x60, 0x0f, 0x00, 0x34, 0xa1, 0xa3, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa,
			0x82, 0x00, 0x80, 0x52, 0xa4, 0x01, 0x00, 0x94, 0xc0, 0x0e, 0x00, 0x34, 0x68, 0x3a, 0x40, 0xf9, 0xa0, 0x03, 0x5a,
			0xf8, 0xa1, 0x03, 0x56, 0xb8, 0xa2, 0x83, 0x55, 0xb8, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x00, 0x00, 0x71, 0xe9, 0x17,
			0x9f, 0x1a, 0x48, 0x01, 0x80, 0x52, 0x6f, 0x00, 0x00, 0x14, 0xa1, 0x83, 0x01, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x02,
			0x01, 0x80, 0x52, 0x96, 0x01, 0x00, 0x94, 0x00, 0x25, 0x00, 0x34, 0xa1, 0xc3, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa,
			0x82, 0x00, 0x80, 0x52, 0x91, 0x01, 0x00, 0x94, 0x60, 0x24, 0x00, 0x34, 0x15, 0x00, 0x80, 0x52, 0xb7, 0x03, 0x5a,
			0xf8, 0xa8, 0x03, 0x55, 0xb8, 0xbf, 0x02, 0x08, 0x6b, 0x20, 0x11, 0x00, 0x54, 0xbf, 0xff, 0x35, 0xa9, 0xe8, 0x5a,
			0x75, 0xf8, 0xa1, 0x83, 0x02, 0xd1, 0xa2, 0xa3, 0x02, 0xd1, 0x94, 0x01, 0x00, 0x94, 0xb5, 0x06, 0x00, 0x11, 0xf7,
			0xff, 0xff, 0x17, 0xa1, 0x83, 0x01, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0x80, 0x01, 0x00, 0x94,
			0x40, 0x0a, 0x00, 0x34, 0xa1, 0x83, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0x7b, 0x01, 0x00,
			0x94, 0xa0, 0x09, 0x00, 0x34, 0xa1, 0xa3, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x42, 0x00, 0x80, 0x52, 0x76, 0x01,
			0x00, 0x94, 0x00, 0x09, 0x00, 0x34, 0xf5, 0x03, 0x00, 0x91, 0xa8, 0x83, 0x55, 0x78, 0x08, 0x71, 0x1d, 0x53, 0x08,
			0x3d, 0x00, 0x11, 0x08, 0x3d, 0x7c, 0x92, 0xe9, 0x03, 0x00, 0x91, 0x37, 0x01, 0x08, 0xcb, 0xff, 0x02, 0x00, 0x91,
			0xa8, 0x83, 0x55, 0x78, 0x02, 0xf1, 0x7d, 0xd3, 0xe0, 0x03, 0x16, 0xaa, 0xe1, 0x03, 0x17, 0xaa, 0x68, 0x01, 0x00,
			0x94, 0x40, 0x1a, 0x00, 0x34, 0xa1, 0xc3, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x42, 0x00, 0x80, 0x52, 0x63, 0x01,
			0x00, 0x94, 0xa0, 0x19, 0x00, 0x34, 0xa8, 0x03, 0x55, 0x78, 0x09, 0x71, 0x1d, 0x53, 0x29, 0x3d, 0x00, 0x11, 0x29,
			0x3d, 0x7c, 0x92, 0xea, 0x03, 0x00, 0x91, 0x41, 0x01, 0x09, 0xcb, 0x3f, 0x00, 0x00, 0x91, 0x02, 0xf1, 0x7d, 0xd3,
			0xe0, 0x03, 0x16, 0xaa, 0xa1, 0x03, 0x14, 0xf8, 0x57, 0x01, 0x00, 0x94, 0x20, 0x18, 0x00, 0x34, 0xb5, 0x83, 0x13,
			0xf8, 0xb9, 0x03, 0x11, 0xf8, 0x15, 0x00, 0x80, 0xd2, 0xbb, 0x03, 0x5a, 0xf8, 0xbc, 0x03, 0x56, 0xf8, 0xba, 0x03,
			0x55, 0x78, 0xbf, 0x02, 0x1a, 0xeb, 0x80, 0x1b, 0x00, 0x54, 0xa8, 0x03, 0x54, 0xf8, 0x19, 0x79, 0x75, 0xf8, 0x28,
			0x03, 0x40, 0xf9, 0x18, 0xf5, 0x73, 0xd3, 0xe8, 0x01, 0xf0, 0xb7, 0x09, 0xfd, 0x6b, 0xd3, 0x0a, 0xa9, 0x40, 0x92,
			0x0b, 0xa9, 0x6a, 0x93, 0x6b, 0x31, 0x55, 0x92, 0x2a, 0x1d, 0x48, 0xb3, 0x49, 0x01, 0x0b, 0xaa, 0x0a, 0x7d, 0x40,
			0x92, 0x1f, 0x01, 0x41, 0xf2, 0x29, 0x01, 0x8a, 0x9a, 0x6a, 0xff, 0x88, 0x8a, 0x4a, 0x01, 0x1c, 0x8b, 0x40, 0x01,
			0x09, 0x8b, 0x48, 0x01, 0xf8, 0xb6, 0x04, 0x00, 0x00, 0x14, 0x09, 0x3d, 0x40, 0x92, 0xe0, 0x7a, 0x69, 0xf8, 0xc8,
			0x00, 0xf8, 0xb6, 0x01, 0xc9, 0x71, 0xd3, 0x03, 0xc1, 0x70, 0xd3, 0x02, 0xbd, 0x60, 0xd3, 0xe4, 0x03, 0x19, 0xaa,
			0x09, 0x01, 0x00, 0x94, 0x20, 0x03, 0x00, 0xf9, 0x39, 0x4f, 0x38, 0x8b, 0xb8, 0xfc, 0xff, 0x35, 0xb5, 0x06, 0x00,
			0x91, 0xdf, 0xff, 0xff, 0x17, 0x09, 0x00, 0x80, 0x52, 0xe8, 0x00, 0x80, 0x52, 0x3f, 0x01, 0x00, 0x71, 0x24, 0x00,
			0x00, 0x14, 0xa1, 0x83, 0x01, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0x26, 0x01, 0x00, 0x94, 0x20,
			0x03, 0x00, 0x34, 0xa1, 0xd3, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x82, 0x00, 0x80, 0x52, 0x21, 0x01, 0x00, 0x94,
			0x80, 0x02, 0x00, 0x34, 0xa1, 0x83, 0x02, 0xd1, 0xe0, 0x03, 0x16, 0xaa, 0x02, 0x01, 0x80, 0x52, 0x1c, 0x01, 0x00,
			0x94, 0xe0, 0x01, 0x00, 0x34, 0x15, 0x00, 0x80, 0x52, 0xb7, 0x03, 0x5a, 0xf8, 0xa8, 0xc3, 0x54, 0xb8, 0xbf, 0x02,
			0x08, 0x6b, 0xc0, 0x0f, 0x00, 0x54, 0xbf, 0x7f, 0x35, 0xa9, 0xa8, 0x03, 0x56, 0xf8, 0xe9, 0x5a, 0x75, 0xb8, 0x08,
			0x01, 0x09, 0x8b, 0xa1, 0xa3, 0x02, 0xd1, 0xa2, 0xc3, 0x02, 0xd1, 0x1d, 0x01, 0x00, 0x94, 0xb5, 0x06, 0x00, 0x11,
			0xf5, 0xff, 0xff, 0x17, 0x09, 0x00, 0x80, 0x52, 0xe8, 0x00, 0x80, 0x52, 0x05, 0x00, 0x00, 0x14, 0x17, 0x00, 0x80,
			0x52, 0xe8, 0x00, 0x80, 0x52, 0xff, 0x02, 0x00, 0x71, 0xe9, 0x07, 0x9f, 0x1a, 0x1f, 0x29, 0x00, 0x71, 0x61, 0x00,
			0x00, 0x54, 0xc9, 0x12, 0x00, 0x36, 0x08, 0x00, 0x80, 0x52, 0x28, 0xde, 0xff, 0x34, 0x91, 0x00, 0x00, 0x14, 0x1a,
			0x0f, 0x15, 0x8b, 0xa9, 0x03, 0x54, 0xf8, 0x28, 0xf1, 0x41, 0x29, 0x39, 0x01, 0x08, 0x8b, 0x28, 0x15, 0x40, 0xb9,
			0x1f, 0x0d, 0x00, 0x71, 0x00, 0x0d, 0x00, 0x54, 0x1f, 0x09, 0x00, 0x71, 0x80, 0x0e, 0x00, 0x54, 0x1f, 0x05, 0x00,
			0x71, 0xa1, 0x01, 0x00, 0x54, 0xa9, 0x03, 0x54, 0xf8, 0x28, 0x09, 0x40, 0xb9, 0x35, 0x01, 0x08, 0x8b, 0xfb, 0x03,
			0x1a, 0xaa, 0x1c, 0x01, 0x00, 0xb4, 0xa8, 0x46, 0x40, 0xb8, 0x01, 0x1d, 0x00, 0x12, 0x03, 0x7d, 0x09, 0x53, 0xef,
			0x00, 0x00, 0x94, 0x60, 0x87, 0x00, 0xf8, 0x9c, 0x07, 0x00, 0xd1, 0x5c, 0xff, 0xff, 0xb5, 0x0a, 0x00, 0x80, 0xd2,
			0xa9, 0x03, 0x54, 0xf8, 0x28, 0x05, 0x40, 0xb9, 0x29, 0x01, 0x08, 0x8b, 0xa8, 0x83, 0x53, 0xf8, 0xfc, 0x02, 0x08,
			0xcb, 0xa9, 0x83, 0x11, 0xf8, 0x28, 0x45, 0x40, 0xb8, 0xa9, 0x23, 0x32, 0xa9, 0xa8, 0x83, 0x52, 0xf8, 0x5f, 0x01,
			0x08, 0xeb, 0xc0, 0x07, 0x00, 0x54, 0xaa, 0x03, 0x13, 0xf8, 0xa8, 0x03, 0x52, 0xf8, 0x08, 0x79, 0x6a, 0xb8, 0xe8,
			0x06, 0x00, 0x34, 0x1b, 0x00, 0x80, 0xd2, 0xa9, 0x83, 0x51, 0xf8, 0x39, 0x01, 0x08, 0x8b, 0x28, 0x2b, 0x40, 0x79,
			0xa8, 0x03, 0x14, 0xf8, 0x28, 0x5b, 0x00, 0x91, 0xa8, 0x83, 0x13, 0xf8, 0xa8, 0x03, 0x54, 0xf8, 0x7f, 0x03, 0x08,
			0xeb, 0xa0, 0x05, 0x00, 0x54, 0x28, 0x07, 0x40, 0xf9, 0xe8, 0x02, 0x08, 0x8b, 0x29, 0x0b, 0x40, 0x79, 0x68, 0x23,
			0x09, 0x9b, 0xa9, 0x83, 0x53, 0xf8, 0x29, 0x79, 0x7b, 0x78, 0x18, 0x01, 0x09, 0x8b, 0x15, 0x03, 0x40, 0xf9, 0xb0,
			0xfe, 0x7e, 0xd3, 0xa2, 0xbe, 0x60, 0xd3, 0xa3, 0xc2, 0x70, 0xd3, 0xa1, 0xca, 0x71, 0xd3, 0x1f, 0x0e, 0x00, 0xf1,
			0x10, 0x92, 0x9f, 0x9a, 0xf1, 0x0c, 0x00, 0x10, 0x1f, 0x20, 0x03, 0xd5, 0x30, 0x7a, 0xb0, 0xb8, 0x30, 0x02, 0x10,
			0x8b, 0x00, 0x02, 0x1f, 0xd6, 0xa8, 0xfe, 0x6b, 0xd3, 0xa9, 0xaa, 0x40, 0x92, 0xaa, 0xaa, 0x6a, 0x93, 0x4a, 0x31,
			0x55, 0x92, 0x09, 0x1d, 0x48, 0xb3, 0x28, 0x01, 0x0a, 0xaa, 0x80, 0x03, 0x08, 0x8b, 0x0c, 0x00, 0x00, 0x14, 0xa8,
			0x3e, 0x40, 0x92, 0x48, 0x7b, 0x68, 0xf8, 0xa9, 0xca, 0x60, 0xd3, 0x00, 0x01, 0x09, 0x8b, 0x07, 0x00, 0x00, 0x14,
			0xe0, 0x42, 0x35, 0x8b, 0x03, 0x00, 0x00, 0x14, 0xa8, 0x3e, 0x40, 0x92, 0x40, 0x7b, 0x68, 0xf8, 0xe4, 0x03, 0x18,
			0xaa, 0x7f, 0x00, 0x00, 0x94, 0x00, 0x03, 0x00, 0xf9, 0xa8, 0xf6, 0x73, 0xd3, 0x18, 0x0f, 0x08, 0x8b, 0xc8, 0xfb,
			0xff, 0xb5, 0x7b, 0x07, 0x00, 0x91, 0xd2, 0xff, 0xff, 0x17, 0xaa, 0x03, 0x53, 0xf8, 0x4a, 0x05, 0x00, 0x91, 0xc1,
			0xff, 0xff, 0x17, 0x68, 0x2e, 0x40, 0xf9, 0xa1, 0x03, 0x5a, 0xf8, 0xa0, 0xc3, 0x50, 0xb8, 0x22, 0x00, 0xa0, 0x52,
			0x00, 0x01, 0x3f, 0xd6, 0xa1, 0x00, 0x00, 0x94, 0x94, 0xff, 0xff, 0x17, 0x29, 0x00, 0x80, 0x52, 0x48, 0x01, 0x80,
			0x52, 0x91, 0xff, 0xff, 0x17, 0x09, 0x00, 0x80, 0x52, 0xe8, 0x00, 0x80, 0x52, 0xbf, 0x02, 0x00, 0x91, 0x67, 0xff,
			0xff, 0x17, 0xa8, 0x03, 0x54, 0xf8, 0x09, 0x8d, 0x40, 0xb8, 0x15, 0x01, 0x09, 0x8b, 0xfb, 0x03, 0x1a, 0xaa, 0xbc,
			0xf4, 0xff, 0xb4, 0xa3, 0xc2, 0x5f, 0xb8, 0xa1, 0x82, 0x5f, 0x78, 0x8d, 0x00, 0x00, 0x94, 0xa8, 0x06, 0x41, 0xf8,
			0x08, 0x00, 0x08, 0x8b, 0x68, 0x87, 0x00, 0xf8, 0x9c, 0x07, 0x00, 0xd1, 0x3c, 0xff, 0xff, 0xb5, 0x9c, 0xff, 0xff,
			0x17, 0xa9, 0x03, 0x54, 0xf8, 0x28, 0x09, 0x40, 0xb9, 0x28, 0x01, 0x08, 0x8b, 0x15, 0x11, 0x00, 0x91, 0xfb, 0x03,
			0x1a, 0xaa, 0xdc, 0xf2, 0xff, 0xb4, 0xa8, 0xc2, 0x5f, 0xb8, 0x01, 0x1d, 0x00, 0x12, 0x03, 0x7d, 0x09, 0x53, 0x7d,
			0x00, 0x00, 0x94, 0xa8, 0x02, 0x80, 0xb9, 0x08, 0x00, 0x08, 0x8b, 0x68, 0x87, 0x00, 0xf8, 0xb5, 0x22, 0x00, 0x91,
			0x9c, 0x07, 0x00, 0xd1, 0xfc, 0xfe, 0xff, 0xb5, 0x8b, 0xff, 0xff, 0x17, 0x79, 0x00, 0x00, 0x94, 0xb5, 0x83, 0x53,
			0xf8, 0xdd, 0xff, 0xff, 0x17, 0x1f, 0x1d, 0x00, 0x71, 0xc1, 0x01, 0x00, 0x54, 0x7f, 0x00, 0x00, 0x94, 0x68, 0x3e,
			0x40, 0xf9, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0xa0, 0xc3, 0x99, 0xb8, 0xbf, 0x43, 0x01, 0xd1, 0xfd,
			0x7b, 0x45, 0xa9, 0xf4, 0x4f, 0x44, 0xa9, 0xf6, 0x57, 0x43, 0xa9, 0xf8, 0x5f, 0x42, 0xa9, 0xfa, 0x67, 0x41, 0xa9,
			0xfc, 0x6f, 0xc6, 0xa8, 0xc0, 0x03, 0x5f, 0xd6, 0xf8, 0xff, 0xff, 0x17, 0x7c, 0xf9, 0xff, 0xff, 0x70, 0xfb, 0xff,
			0xff, 0xd8, 0xf9, 0xff, 0xff, 0xb8, 0xfa, 0xff, 0xff, 0x18, 0xfb, 0xff, 0xff, 0xd8, 0xfc, 0xff, 0xff, 0x78, 0xfe,
			0xff, 0xff, 0x98, 0xfe, 0xff, 0xff, 0xac, 0xfe, 0xff, 0xff, 0xb4, 0xfe, 0xff, 0xff, 0xf8, 0x5f, 0xbc, 0xa9, 0xf6,
			0x57, 0x01, 0xa9, 0xf4, 0x4f, 0x02, 0xa9, 0xfd, 0x7b, 0x03, 0xa9, 0xfd, 0xc3, 0x00, 0x91, 0xf3, 0x03, 0x04, 0xaa,
			0xf4, 0x03, 0x03, 0xaa, 0xf5, 0x03, 0x02, 0xaa, 0xf6, 0x03, 0x01, 0xaa, 0xf7, 0x03, 0x00, 0xaa, 0x43, 0x00, 0x00,
			0xb4, 0x9f, 0x02, 0x00, 0xf9, 0xf5, 0x02, 0x00, 0xb4, 0x68, 0x1a, 0x40, 0xf9, 0xe0, 0x03, 0x17, 0xaa, 0xe1, 0x03,
			0x16, 0xaa, 0xe2, 0x03, 0x15, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0xb1, 0xe1, 0x00, 0x00, 0x54, 0x68,
			0x42, 0x40, 0xf9, 0x00, 0x01, 0x3f, 0xd6, 0x08, 0x00, 0x40, 0xb9, 0x1f, 0x11, 0x00, 0x71, 0xa0, 0xfe, 0xff, 0x54,
			0x0c, 0x00, 0x00, 0x14, 0x1f, 0x04, 0x00, 0xf1, 0x4b, 0x01, 0x00, 0x54, 0x94, 0x00, 0x00, 0xb4, 0x88, 0x02, 0x40,
			0xf9, 0x08, 0x01, 0x00, 0x8b, 0x88, 0x02, 0x00, 0xf9, 0xd6, 0x02, 0x00, 0x8b, 0xb5, 0x02, 0x00, 0xcb, 0x75, 0xfd,
			0xff, 0xb5, 0x20, 0x00, 0x80, 0x52, 0x02, 0x00, 0x00, 0x14, 0x00, 0x00, 0x80, 0x52, 0xfd, 0x7b, 0x43, 0xa9, 0xf4,
			0x4f, 0x42, 0xa9, 0xf6, 0x57, 0x41, 0xa9, 0xf8, 0x5f, 0xc4, 0xa8, 0xc0, 0x03, 0x5f, 0xd6, 0x44, 0x3c, 0x50, 0xb3,
			0x7f, 0x00, 0x00, 0x71, 0x48, 0x00, 0x84, 0x9a, 0x3f, 0x0c, 0x00, 0x71, 0x08, 0x02, 0x00, 0x54, 0xf0, 0x03, 0x01,
			0xaa, 0x1f, 0x0e, 0x00, 0xf1, 0x10, 0x92, 0x9f, 0x9a, 0xb1, 0x01, 0x00, 0x10, 0x1f, 0x20, 0x03, 0xd5, 0x30, 0x7a,
			0xb0, 0xb8, 0x30, 0x02, 0x10, 0x8b, 0x00, 0x02, 0x1f, 0xd6, 0x00, 0x01, 0xc1, 0xda, 0xc0, 0x03, 0x5f, 0xd6, 0x00,
			0x05, 0xc1, 0xda, 0xc0, 0x03, 0x5f, 0xd6, 0x00, 0x09, 0xc1, 0xda, 0xc0, 0x03, 0x5f, 0xd6, 0x00, 0x0d, 0xc1, 0xda,
			0xc0, 0x03, 0x5f, 0xd6, 0xe0, 0xff, 0xff, 0xff, 0xe8, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff,
			0xff, 0x28, 0x04, 0x00, 0x71, 0xcb, 0x01, 0x00, 0x54, 0xfd, 0x7b, 0xbf, 0xa9, 0xfd, 0x03, 0x00, 0x91, 0x49, 0x40,
			0x23, 0x8b, 0xea, 0x03, 0x09, 0xaa, 0x4b, 0x15, 0x40, 0x38, 0x7f, 0x7d, 0x01, 0x71, 0x41, 0x01, 0x89, 0x9a, 0x89,
			0x34, 0x40, 0xf9, 0x00, 0xd8, 0x68, 0xf8, 0x20, 0x01, 0x3f, 0xd6, 0xe0, 0x43, 0xc1, 0xda, 0xfd, 0x7b, 0xc1, 0xa8,
			0xc0, 0x03, 0x5f, 0xd6, 0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6, 0x03, 0x00, 0x80, 0xd2, 0xe4, 0x03, 0x13,
			0xaa, 0xa9, 0xff, 0xff, 0x17, 0xe0, 0x03, 0x18, 0xaa, 0xe2, 0x03, 0x19, 0xaa, 0xe4, 0x03, 0x13, 0xaa, 0xe9, 0xff,
			0xff, 0x17, 0x29, 0x00, 0x80, 0x52, 0x48, 0x01, 0x80, 0x52, 0xb9, 0x03, 0x51, 0xf8, 0x3b, 0x00, 0x80, 0x52, 0x1c,
			0xfd, 0x9f, 0x52, 0xfc, 0xff, 0xaf, 0x72, 0xc0, 0x03, 0x5f, 0xd6, 0xa4, 0x93, 0x01, 0xd1, 0x00, 0x00, 0x80, 0x52,
			0xe3, 0x03, 0x19, 0xaa, 0x00, 0x01, 0x1f, 0xd6, 0x68, 0x3e, 0x40, 0xf9, 0xe0, 0x03, 0x16, 0xaa, 0x00, 0x01, 0x1f,
			0xd6, 0xa0, 0x03, 0x5a, 0xf8, 0xa1, 0x03, 0x56, 0xf8, 0x00, 0x01, 0x1f, 0xd6
		};

		private static bool is_dyld_stub_binder (string module_name, string symbol_name) {
			return module_name == "/usr/lib/libSystem.B.dylib" && symbol_name == "dyld_stub_binder";
		}

		private size_t compute_virtual_size (Gum.DarwinModule module) {
			size_t total = 0;

			foreach (unowned Gum.DarwinSegment segment in module.segments.data) {
				size_t size = (size_t) segment.vm_size;

				size_t remainder = size % page_size;
				if (remainder != 0)
					size += page_size - remainder;

				total += size;
			}

			return total;
		}

		private uint64 resolve_dyld_symbol (string name, string nick) throws Error {
			uint64? val = dyld_symbols[name];
			if (val == null)
				throw new Error.UNSUPPORTED ("Unsupported iOS version (%s not found)", nick);
			return val;
		}

		private async void ensure_libsystem_initialized (Cancellable? cancellable) throws GLib.Error {
			if (libsystem_initialized)
				return;

			yield restore_main_thread_state (cancellable);

			LLDB.Breakpoint? modern_breakpoint = null;
			uint64 launch_with_closure = 0;

			const string[] launch_with_closure_names = {
				"__ZN4dyldL17launchWithClosureEPKN5dyld37closure13LaunchClosureEPK15DyldSharedCachePKNS0_11MachOLoadedEmiPPKcSD_SD_R11DiagnosticsPmSG_PbSH_",
				"__ZN4dyldL17launchWithClosureEPKN5dyld312launch_cache13binary_format7ClosureEPK15DyldSharedCachePK11mach_headermiPPKcSE_SE_PmSF_",
				"__ZN4dyldL17launchWithClosureEPKN5dyld37closure13LaunchClosureEPK15DyldSharedCachePKNS0_11MachOLoadedEmiPPKcSD_SD_PmSE_",
			};
			foreach (var candidate in launch_with_closure_names) {
				if (dyld_symbols.has_key (candidate)) {
					launch_with_closure = dyld_symbols[candidate];
					break;
				}
			}
			if (launch_with_closure != 0) {
				uint64 run_initializers_call = yield find_dyld3_run_initializers_call (launch_with_closure, cancellable);
				modern_breakpoint = yield lldb.add_breakpoint (run_initializers_call, cancellable);
			}

			uint64 initialize_main_executable = resolve_dyld_symbol ("__ZN4dyld24initializeMainExecutableEv", "initializeMainExecutable");
			LLDB.Breakpoint legacy_breakpoint = yield lldb.add_breakpoint (initialize_main_executable, cancellable);

			var exception = yield lldb.continue_until_exception (cancellable);

			LLDB.Breakpoint? hit_breakpoint = exception.breakpoint;
			if (hit_breakpoint == null)
				throw new Error.UNSUPPORTED ("Unexpected exception");

			yield legacy_breakpoint.remove (cancellable);

			if (modern_breakpoint != null)
				yield modern_breakpoint.remove (cancellable);

			if (hit_breakpoint == legacy_breakpoint)
				yield initialize_libsystem_from_legacy_codepath (cancellable);
			else
				assert (hit_breakpoint == modern_breakpoint);

			yield save_main_thread_state (cancellable);

			yield lldb.write_bool (dyld_fields.libsystem_initialized, true, cancellable);
			libsystem_initialized = true;
		}

		private async uint64 find_dyld3_run_initializers_call (uint64 launch_with_closure, Cancellable? cancellable)
				throws GLib.Error {
			size_t max_size = 2048;
			var buffer = yield lldb.read_buffer (launch_with_closure, max_size, cancellable);

			/*
			 * Need to find code near “dyld3: launch, running initializers”, which is right
			 * before the call to runInitialzersBottomUp() (yes, Apple misspelled it).
			 *
			 * Which may look something like:
			 *
			 *     f9401788       ldr x8, [x28, 0x28]
			 *
			 * Broken down this is:
			 *
			 *                     0x28 (5*4)   x28    x8
			 *                         |         |     |
			 *     11 111 0 01 01 000000000101 11100 01000
			 *
			 * We will find a matching LDR with an unsigned offset of 0x28, ignoring the
			 * actual registers used. I.e.:
			 *
			 *     11 111 0 01 01 000000000101 xxxxx xxxxx
			 *
			 */
			for (size_t offset = 0; offset != max_size; offset += 4) {
				uint32 insn = buffer.read_uint32 (offset);
				if ((insn & 0xfffffc00U) == 0xf9401400U) {
					var result = launch_with_closure + offset;
					return result;
				}
			}

			throw new Error.UNSUPPORTED ("Unable to probe dyld v3 internals; please file a bug");
		}

		private async void initialize_libsystem_from_legacy_codepath (Cancellable? cancellable) throws GLib.Error {
			uint64 code = jit_page;

			var code_builder_pac = lldb.make_buffer_builder ();

			uint64 sign_pointer = code;
			code_builder_pac
				.append_uint32 (0xdac123e0U)  // paciza x0
				.append_uint32 (0xd65f03c0U); // ret

			yield lldb.write_byte_array (code, code_builder_pac.build (), cancellable);

			var code_builder = lldb.make_buffer_builder ();

			uint64 ret_gadget = code;
			code_builder
				.append_uint32 (0xd65f03c0U); // ret

			uint64 get_thread_buf = code + code_builder.offset;
			code_builder
				.append_uint32 (0x58000060U)  // ldr x0, #0xc
				.append_uint32 (0xd65f03c0U); // ret

			size_t error_buf_literal_offset = code_builder.skip (4).offset;

			yield save_main_thread_state (cancellable);

			var invalid_as_nop = new InvalidAsNopHandler ();

			uint64 signed_ret_gadget = yield invoke_remote_function (sign_pointer, {
					ret_gadget
				}, invalid_as_nop, cancellable);

			uint64 signed_get_thread_buf = yield invoke_remote_function (sign_pointer, {
					get_thread_buf
				}, invalid_as_nop, cancellable);

			var helpers_builder = lldb.make_buffer_builder ();

			uint64 helpers_version = 1;
			uint64 acquire_global_dyld_lock = signed_ret_gadget;
			uint64 release_global_dyld_lock = signed_ret_gadget;
			uint64 get_thread_buffer_for_dlerror = signed_get_thread_buf;

			helpers_builder
				.append_pointer (helpers_version)
				.append_pointer (acquire_global_dyld_lock)
				.append_pointer (release_global_dyld_lock)
				.append_pointer (get_thread_buffer_for_dlerror);

			size_t libsystem_string_offset = helpers_builder.offset;
			helpers_builder.append_string ("/usr/lib/libSystem.B.dylib");

			size_t error_buffer_offset = helpers_builder.offset;
			const uint error_buffer_size = 1024;
			helpers_builder.skip (error_buffer_size);

			var helpers_buf = helpers_builder.build ();
			uint64 helpers = scratch_page;
			yield lldb.write_byte_array (helpers, helpers_buf, cancellable);

			uint64 libsystem_string = helpers + libsystem_string_offset;

			code_builder.write_pointer (error_buf_literal_offset, helpers + error_buffer_offset);
			yield lldb.write_byte_array (code, code_builder.build (), cancellable);

			uint64 register_thread_helpers =
				resolve_dyld_symbol ("__ZL21registerThreadHelpersPKN4dyld16LibSystemHelpersE", "registerThreadHelpers");
			yield invoke_remote_function (register_thread_helpers, {
					helpers
				}, null, cancellable);

			/*
			var strcmp_impl = resolve_dyld_symbol ("_strcmp", "strcmp");
			var modinit_start = resolve_dyld_symbol (
				"__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE",
				"doModInitStart");
			var modinit_end = resolve_dyld_symbol (
				"__ZN16ImageLoaderMachO16doGetDOFSectionsERKN11ImageLoader11LinkContextERNSt3__16vectorINS0_7DOFInfoENS4_9allocatorIS6_EEEE",
				"doModInitEnd");
			var strcmp_handler = new ModinitStrcmpHandler (strcmp_impl, modinit_start, modinit_end);
			var strcmp_breakpoint = yield lldb.add_breakpoint (strcmp_impl, cancellable);
			*/
			ExceptionHandler? strcmp_handler = null;

			var dlopen = dyld_symbols.has_key ("_dlopen_internal")
				? resolve_dyld_symbol ("_dlopen_internal", "dlopen")
				: resolve_dyld_symbol ("_dlopen", "dlopen");
			yield invoke_remote_function (dlopen, {
					libsystem_string,
					9,
					0
				}, strcmp_handler, cancellable);

			/*
			yield strcmp_breakpoint.remove (cancellable);
			*/

			yield restore_main_thread_state (cancellable);
		}

		private class ModinitStrcmpHandler : Object, ExceptionHandler {
			public uint64 strcmp_impl {
				get;
				construct;
			}

			public uint64 modinit_start {
				get;
				construct;
			}

			public uint64 modinit_end {
				get;
				construct;
			}

			public ModinitStrcmpHandler (uint64 strcmp_impl, uint64 modinit_start, uint64 modinit_end) {
				Object (
					strcmp_impl: strcmp_impl,
					modinit_start: modinit_start,
					modinit_end: modinit_end
				);
			}

			public async bool try_handle_exception (LLDB.Exception exception, Cancellable? cancellable) throws GLib.Error {
				uint64 pc = exception.context["pc"];
				if (pc != strcmp_impl)
					return false;

				var thread = exception.thread;

				uint64 ret_address = exception.context["lr"];
				if (ret_address >= modinit_start && ret_address < modinit_end) {
					yield thread.write_register ("x0", 0, cancellable);
					yield thread.write_register ("pc", ret_address, cancellable);
				}

				return true;
			}
		}

		private class InvalidAsNopHandler : Object, ExceptionHandler {
			public async bool try_handle_exception (LLDB.Exception exception, Cancellable? cancellable) throws GLib.Error {
				if (exception.metype != EXC_BAD_INSTRUCTION)
					return false;

				uint64 pc = exception.context["pc"];
				var thread = exception.thread;
				yield thread.write_register ("pc", pc + 4, cancellable);

				return true;
			}
		}

		private async Gee.HashMap<string, uint64?> fetch_dyld_symbols (Cancellable? cancellable) throws GLib.Error {
			var symbols = new Gee.HashMap<string, uint64?> ();

			uint64 code = jit_page;
			yield lldb.write_byte_array (code, new Bytes.static (SYMBOL_FETCHER_CODE), cancellable);

			uint64 output = scratch_page;

			size_t length = (size_t) yield invoke_remote_function (code, {
					output,
					dyld_base
				}, null, cancellable);
			size_t size = length + 1;
			if (size > page_size)
				throw new Error.UNSUPPORTED ("Buffer too small; please file a bug");

			Bytes output_bytes = yield lldb.read_byte_array (output, size, cancellable);
			unowned string output_str = (string) output_bytes.get_data ();
			foreach (var line in output_str.split ("\n")) {
				var tokens = line.split ("\t", 2);

				unowned string raw_address = tokens[0];
				unowned string name = tokens[1];

				uint64 address;
				uint64.from_string (raw_address, out address, 16);

				symbols[name] = address;
			}

			return symbols;
		}

		/* Compiled from helpers/symbol-fetcher.c */
		private const uint8[] SYMBOL_FETCHER_CODE = {
			0xff, 0xc3, 0x01, 0xd1, 0xfc, 0x6f, 0x01, 0xa9, 0xfa, 0x67, 0x02, 0xa9, 0xf8, 0x5f, 0x03, 0xa9, 0xf6, 0x57, 0x04,
			0xa9, 0xf4, 0x4f, 0x05, 0xa9, 0xfd, 0x7b, 0x06, 0xa9, 0xfd, 0x83, 0x01, 0x91, 0xf4, 0x03, 0x01, 0xaa, 0xe0, 0x07,
			0x00, 0xf9, 0x13, 0x00, 0x80, 0xd2, 0x19, 0x00, 0x80, 0xd2, 0x38, 0x80, 0x00, 0x91, 0x3a, 0x10, 0x40, 0xb9, 0x55,
			0x19, 0x00, 0x30, 0x1f, 0x20, 0x03, 0xd5, 0x56, 0x19, 0x00, 0x10, 0x1f, 0x20, 0x03, 0xd5, 0xfa, 0x03, 0x00, 0x34,
			0x08, 0x03, 0x40, 0xb9, 0x1f, 0x09, 0x00, 0x71, 0x80, 0x01, 0x00, 0x54, 0x1f, 0x2d, 0x00, 0x71, 0x80, 0x01, 0x00,
			0x54, 0x1f, 0x65, 0x00, 0x71, 0x61, 0x01, 0x00, 0x54, 0x17, 0x23, 0x00, 0x91, 0xe0, 0x03, 0x17, 0xaa, 0xe1, 0x03,
			0x15, 0xaa, 0x93, 0x00, 0x00, 0x94, 0x60, 0x01, 0x00, 0x34, 0x13, 0x0f, 0x40, 0xf9, 0x04, 0x00, 0x00, 0x14, 0xfc,
			0x03, 0x18, 0xaa, 0x02, 0x00, 0x00, 0x14, 0xfb, 0x03, 0x18, 0xaa, 0x08, 0x07, 0x40, 0xb9, 0x18, 0x03, 0x08, 0x8b,
			0x5a, 0x07, 0x00, 0x51, 0x9a, 0xfd, 0xff, 0x35, 0x09, 0x00, 0x00, 0x14, 0xe0, 0x03, 0x17, 0xaa, 0xe1, 0x03, 0x16,
			0xaa, 0x85, 0x00, 0x00, 0x94, 0x00, 0xff, 0xff, 0x34, 0x08, 0x0f, 0x40, 0xf9, 0x09, 0x17, 0x40, 0xf9, 0x19, 0x01,
			0x09, 0xcb, 0xf4, 0xff, 0xff, 0x17, 0x88, 0x02, 0x13, 0xcb, 0x28, 0x03, 0x08, 0x8b, 0x89, 0x0b, 0x40, 0xb9, 0x19,
			0x01, 0x09, 0x8b, 0x89, 0x13, 0x40, 0xb9, 0x13, 0x01, 0x09, 0x8b, 0x78, 0x0b, 0x40, 0xb9, 0x96, 0x14, 0x00, 0x70,
			0x1f, 0x20, 0x03, 0xd5, 0xf7, 0x07, 0x40, 0xf9, 0x45, 0x00, 0x00, 0x14, 0xf5, 0x03, 0x18, 0x2a, 0xa8, 0xee, 0x7c,
			0xd3, 0x28, 0x6b, 0x68, 0xb8, 0x7c, 0x02, 0x08, 0x8b, 0xe0, 0x03, 0x1c, 0xaa, 0x41, 0x0f, 0x00, 0x10, 0x1f, 0x20,
			0x03, 0xd5, 0x4b, 0x00, 0x00, 0x94, 0xe0, 0x03, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x21, 0x0f, 0x00, 0x50, 0x1f,
			0x20, 0x03, 0xd5, 0x46, 0x00, 0x00, 0x94, 0x40, 0x03, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x41, 0x0f, 0x00, 0x70,
			0x1f, 0x20, 0x03, 0xd5, 0x41, 0x00, 0x00, 0x94, 0xa0, 0x02, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x61, 0x0f, 0x00,
			0x30, 0x1f, 0x20, 0x03, 0xd5, 0x54, 0x00, 0x00, 0x94, 0x00, 0x02, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x0f,
			0x00, 0x30, 0x1f, 0x20, 0x03, 0xd5, 0x4f, 0x00, 0x00, 0x94, 0x60, 0x01, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0xa1,
			0x0e, 0x00, 0x30, 0x1f, 0x20, 0x03, 0xd5, 0x32, 0x00, 0x00, 0x94, 0xc0, 0x00, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa,
			0xa1, 0x0e, 0x00, 0x10, 0x1f, 0x20, 0x03, 0xd5, 0x2d, 0x00, 0x00, 0x94, 0xa0, 0x03, 0x00, 0x34, 0x7a, 0x00, 0x00,
			0x34, 0x48, 0x01, 0x80, 0x52, 0xe8, 0x16, 0x00, 0x38, 0x08, 0x00, 0x80, 0x52, 0x29, 0x13, 0x15, 0x8b, 0x29, 0x05,
			0x40, 0xf9, 0x89, 0x02, 0x09, 0x8b, 0xea, 0x0f, 0x1e, 0x32, 0x0b, 0x00, 0x00, 0x14, 0xeb, 0x03, 0x0a, 0x2a, 0x2b,
			0x25, 0xcb, 0x9a, 0x6b, 0x0d, 0x40, 0x92, 0x7f, 0x01, 0x00, 0x71, 0xec, 0x07, 0x9f, 0x1a, 0x08, 0x01, 0x0c, 0x2a,
			0x68, 0x00, 0x00, 0x36, 0xcb, 0x6a, 0x6b, 0x38, 0xeb, 0x16, 0x00, 0x38, 0x4a, 0x11, 0x00, 0x51, 0x5f, 0x11, 0x00,
			0x31, 0xa1, 0xfe, 0xff, 0x54, 0x28, 0x01, 0x80, 0x52, 0x02, 0x00, 0x00, 0x14, 0x9c, 0x07, 0x00, 0x91, 0xe8, 0x16,
			0x00, 0x38, 0x88, 0x03, 0x40, 0x39, 0xa8, 0xff, 0xff, 0x35, 0x5a, 0x07, 0x00, 0x11, 0x18, 0x07, 0x00, 0x11, 0x68,
			0x0f, 0x40, 0xb9, 0x1f, 0x03, 0x08, 0x6b, 0x41, 0xf7, 0xff, 0x54, 0xe8, 0x07, 0x40, 0xf9, 0xe0, 0x02, 0x08, 0xcb,
			0xff, 0x02, 0x00, 0x39, 0xfd, 0x7b, 0x46, 0xa9, 0xf4, 0x4f, 0x45, 0xa9, 0xf6, 0x57, 0x44, 0xa9, 0xf8, 0x5f, 0x43,
			0xa9, 0xfa, 0x67, 0x42, 0xa9, 0xfc, 0x6f, 0x41, 0xa9, 0xff, 0xc3, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6, 0xf6, 0x57,
			0xbd, 0xa9, 0xf4, 0x4f, 0x01, 0xa9, 0xfd, 0x7b, 0x02, 0xa9, 0xfd, 0x83, 0x00, 0x91, 0xf4, 0x03, 0x01, 0xaa, 0xf3,
			0x03, 0x00, 0xaa, 0x35, 0x00, 0x40, 0x39, 0x08, 0x00, 0x00, 0x14, 0x1f, 0x01, 0x15, 0x6b, 0xa1, 0x00, 0x00, 0x54,
			0xe0, 0x03, 0x13, 0xaa, 0xe1, 0x03, 0x14, 0xaa, 0x0c, 0x00, 0x00, 0x94, 0xa0, 0x00, 0x00, 0x37, 0x73, 0x06, 0x00,
			0x91, 0x68, 0x02, 0x40, 0x39, 0x08, 0xff, 0xff, 0x35, 0x13, 0x00, 0x80, 0xd2, 0x7f, 0x02, 0x00, 0xf1, 0xe0, 0x07,
			0x9f, 0x1a, 0xfd, 0x7b, 0x42, 0xa9, 0xf4, 0x4f, 0x41, 0xa9, 0xf6, 0x57, 0xc3, 0xa8, 0xc0, 0x03, 0x5f, 0xd6, 0x28,
			0x00, 0x40, 0x39, 0xe8, 0x00, 0x00, 0x34, 0x21, 0x04, 0x00, 0x91, 0x09, 0x14, 0x40, 0x38, 0x3f, 0x01, 0x08, 0x6b,
			0x60, 0xff, 0xff, 0x54, 0x00, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6, 0xe0, 0x03, 0x00, 0x32, 0xc0, 0x03, 0x5f,
			0xd6, 0x08, 0x00, 0x40, 0x39, 0x29, 0x00, 0x40, 0x39, 0x1f, 0x01, 0x09, 0x6b, 0xc1, 0x00, 0x00, 0x54, 0x00, 0x04,
			0x00, 0x91, 0x21, 0x04, 0x00, 0x91, 0x48, 0xff, 0xff, 0x35, 0xe0, 0x03, 0x00, 0x32, 0xc0, 0x03, 0x5f, 0xd6, 0x00,
			0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x57, 0x69, 0x74, 0x68, 0x43, 0x6c,
			0x6f, 0x73, 0x75, 0x72, 0x65, 0x00, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4d, 0x61, 0x69,
			0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x00, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
			0x72, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x73, 0x00, 0x5f, 0x64, 0x6c, 0x6f,
			0x70, 0x65, 0x6e, 0x00, 0x5f, 0x73, 0x74, 0x72, 0x63, 0x6d, 0x70, 0x00, 0x64, 0x6f, 0x4d, 0x6f, 0x64, 0x49, 0x6e,
			0x69, 0x74, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x00, 0x64, 0x6f, 0x47, 0x65, 0x74, 0x44, 0x4f,
			0x46, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x00, 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x5f, 0x5f,
			0x4c, 0x49, 0x4e, 0x4b, 0x45, 0x44, 0x49, 0x54, 0x00, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
			0x61, 0x62, 0x63, 0x64, 0x65, 0x66
		};

		private async SymbolSet resolve_symbols (SymbolQuery query, Cancellable? cancellable) throws GLib.Error {
			uint64 code = jit_page;
			yield lldb.write_byte_array (code, new Bytes.static (SYMBOL_RESOLVER_CODE), cancellable);

			var state_builder = lldb.make_buffer_builder ();

			var strv_builder = new StringVectorBuilder (state_builder);
			uint num_symbols = 0;

			foreach (var group in query.groups) {
				unowned string module_name = group.module_name;

				strv_builder.append_string (module_name);

				foreach (var symbol_name in group.symbol_names) {
					strv_builder.append_string (demangle (symbol_name));

					num_symbols++;
				}

				strv_builder.append_terminator ();
			}
			strv_builder.append_terminator ();

			var input_vector_offset = strv_builder.append_placeholder ();

			var output_vector_offset = state_builder.offset;
			size_t output_vector_size = num_symbols * pointer_size;
			state_builder.skip (output_vector_size);

			var state_size = state_builder.offset;
			uint64 state;
			if (state_size <= page_size)
				state = scratch_page;
			else
				state = yield lldb.allocate (state_size, "rw", cancellable);
			strv_builder.build (state);
			yield lldb.write_byte_array (state, state_builder.build (), cancellable);

			uint64 input_vector_address = state + input_vector_offset;
			uint64 output_vector_address = state + output_vector_offset;

			yield invoke_remote_function (code, {
					input_vector_address,
					output_vector_address,
					dyld_fields.all_image_info
				}, null, cancellable);

			var symbols = new Gee.HashMap<string, Gee.HashMap<string, uint64?>> ();
			var output_vector = yield lldb.read_buffer (output_vector_address, output_vector_size, cancellable);
			size_t offset = 0;
			foreach (var group in query.groups) {
				var result_group = new Gee.HashMap<string, uint64?> ();
				symbols[group.module_name] = result_group;

				foreach (var symbol_name in group.symbol_names) {
					result_group[symbol_name] = lldb.strip_code_address (output_vector.read_pointer (offset));

					offset += pointer_size;
				}
			}

			if (state != scratch_page)
				yield lldb.deallocate (state, cancellable);

			return new SymbolSet (symbols);
		}

		/* Compiled from helpers/symbol-resolver.c */
		private const uint8[] SYMBOL_RESOLVER_CODE = {
			0xff, 0xc3, 0x01, 0xd1, 0xfc, 0x6f, 0x01, 0xa9, 0xfa, 0x67, 0x02, 0xa9, 0xf8, 0x5f, 0x03, 0xa9, 0xf6, 0x57, 0x04,
			0xa9, 0xf4, 0x4f, 0x05, 0xa9, 0xfd, 0x7b, 0x06, 0xa9, 0xfd, 0x83, 0x01, 0x91, 0xf5, 0x03, 0x02, 0xaa, 0xf3, 0x03,
			0x01, 0xaa, 0xf4, 0x03, 0x00, 0xaa, 0x08, 0x00, 0x80, 0xd2, 0x49, 0x04, 0x40, 0xb9, 0x0a, 0x03, 0x80, 0x52, 0xeb,
			0x07, 0x7d, 0xb2, 0x37, 0x2d, 0x0a, 0x9b, 0x76, 0x19, 0x00, 0x30, 0x1f, 0x20, 0x03, 0xd5, 0x18, 0x61, 0x00, 0x91,
			0xff, 0x02, 0x18, 0xeb, 0x60, 0x01, 0x00, 0x54, 0xb9, 0x06, 0x40, 0xf9, 0x28, 0x03, 0x08, 0x8b, 0x00, 0x05, 0x40,
			0xf9, 0xe1, 0x03, 0x16, 0xaa, 0xad, 0x00, 0x00, 0x94, 0xe8, 0x03, 0x18, 0xaa, 0xe0, 0xfe, 0xff, 0x34, 0x28, 0x03,
			0x18, 0x8b, 0x08, 0x81, 0x5e, 0xf8, 0x02, 0x00, 0x00, 0x14, 0x08, 0x00, 0x80, 0xd2, 0x19, 0x00, 0x80, 0xd2, 0xe8,
			0x7f, 0x00, 0xa9, 0x16, 0x00, 0x80, 0xd2, 0x1b, 0x00, 0x80, 0xd2, 0x1c, 0x81, 0x00, 0x91, 0x1a, 0x11, 0x40, 0xb9,
			0x58, 0x04, 0x80, 0x52, 0x18, 0x00, 0xb0, 0x72, 0x75, 0x06, 0x80, 0x52, 0x15, 0x00, 0xb0, 0x72, 0x5a, 0x04, 0x00,
			0x34, 0x88, 0x03, 0x40, 0xb9, 0x1f, 0x01, 0x18, 0x6b, 0xa0, 0x01, 0x00, 0x54, 0x1f, 0x01, 0x15, 0x6b, 0xa0, 0x01,
			0x00, 0x54, 0x1f, 0x65, 0x00, 0x71, 0x81, 0x01, 0x00, 0x54, 0x97, 0x23, 0x00, 0x91, 0xe0, 0x03, 0x17, 0xaa, 0x41,
			0x14, 0x00, 0x70, 0x1f, 0x20, 0x03, 0xd5, 0x90, 0x00, 0x00, 0x94, 0x60, 0x01, 0x00, 0x34, 0x99, 0x0f, 0x40, 0xf9,
			0x04, 0x00, 0x00, 0x14, 0xf6, 0x03, 0x1c, 0xaa, 0x02, 0x00, 0x00, 0x14, 0xfb, 0x03, 0x1c, 0xaa, 0x88, 0x07, 0x40,
			0xb9, 0x9c, 0x03, 0x08, 0x8b, 0x5a, 0x07, 0x00, 0x51, 0x7a, 0xfd, 0xff, 0x35, 0x0b, 0x00, 0x00, 0x14, 0xe0, 0x03,
			0x17, 0xaa, 0xa1, 0x12, 0x00, 0x50, 0x1f, 0x20, 0x03, 0xd5, 0x81, 0x00, 0x00, 0x94, 0xe0, 0xfe, 0xff, 0x34, 0x88,
			0x0f, 0x40, 0xf9, 0x89, 0x17, 0x40, 0xf9, 0x08, 0x01, 0x09, 0xcb, 0xe8, 0x07, 0x00, 0xf9, 0xf2, 0xff, 0xff, 0x17,
			0x96, 0x00, 0x00, 0xb4, 0xc8, 0xa2, 0x00, 0x91, 0xf7, 0x03, 0x40, 0xf9, 0x04, 0x00, 0x00, 0x14, 0xf7, 0x03, 0x40,
			0xf9, 0xbb, 0x05, 0x00, 0xb4, 0x68, 0x23, 0x00, 0x91, 0xe9, 0x02, 0x19, 0xcb, 0xea, 0x07, 0x40, 0xf9, 0x49, 0x01,
			0x09, 0x8b, 0x08, 0x01, 0x40, 0xb9, 0x35, 0x01, 0x08, 0x8b, 0x61, 0x0f, 0x00, 0x10, 0x1f, 0x20, 0x03, 0xd5, 0xe0,
			0x03, 0x15, 0xaa, 0x31, 0x00, 0x00, 0x94, 0xf6, 0x02, 0x00, 0x8b, 0x01, 0x0f, 0x00, 0x10, 0x1f, 0x20, 0x03, 0xd5,
			0xe0, 0x03, 0x15, 0xaa, 0x2c, 0x00, 0x00, 0x94, 0xf7, 0x02, 0x00, 0x8b, 0x80, 0x02, 0x40, 0xf9, 0x60, 0x02, 0x00,
			0xb4, 0x21, 0x01, 0x80, 0x52, 0xc0, 0x02, 0x3f, 0xd6, 0x40, 0x01, 0x00, 0xb4, 0xf5, 0x03, 0x00, 0xaa, 0x94, 0x42,
			0x00, 0x91, 0x81, 0x82, 0x5f, 0xf8, 0x01, 0xff, 0xff, 0xb4, 0xe0, 0x03, 0x15, 0xaa, 0xe0, 0x02, 0x3f, 0xd6, 0x60,
			0x86, 0x00, 0xf8, 0x94, 0x22, 0x00, 0x91, 0xfa, 0xff, 0xff, 0x17, 0x94, 0x42, 0x00, 0x91, 0x88, 0x82, 0x5f, 0xf8,
			0x08, 0xfe, 0xff, 0xb4, 0x7f, 0x86, 0x00, 0xf8, 0x94, 0x22, 0x00, 0x91, 0xfc, 0xff, 0xff, 0x17, 0xfd, 0x7b, 0x46,
			0xa9, 0xf4, 0x4f, 0x45, 0xa9, 0xf6, 0x57, 0x44, 0xa9, 0xf8, 0x5f, 0x43, 0xa9, 0xfa, 0x67, 0x42, 0xa9, 0xfc, 0x6f,
			0x41, 0xa9, 0xff, 0xc3, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6, 0x15, 0x00, 0x80, 0xd2, 0xd9, 0xff, 0xff, 0x17, 0xe8,
			0x03, 0x00, 0xaa, 0x0a, 0x00, 0x80, 0xd2, 0x00, 0x00, 0x80, 0xd2, 0x09, 0x01, 0x40, 0xf9, 0x2b, 0x15, 0x40, 0x38,
			0x6c, 0x19, 0x40, 0x92, 0x8c, 0x21, 0xca, 0x9a, 0x80, 0x01, 0x00, 0xaa, 0x4a, 0x1d, 0x00, 0x91, 0x6b, 0xff, 0x3f,
			0x37, 0x09, 0x01, 0x00, 0xf9, 0xc0, 0x03, 0x5f, 0xd6, 0xff, 0x43, 0x01, 0xd1, 0xf8, 0x5f, 0x01, 0xa9, 0xf6, 0x57,
			0x02, 0xa9, 0xf4, 0x4f, 0x03, 0xa9, 0xfd, 0x7b, 0x04, 0xa9, 0xfd, 0x03, 0x01, 0x91, 0xf4, 0x03, 0x01, 0xaa, 0xf3,
			0x03, 0x00, 0xaa, 0xe8, 0x03, 0x00, 0xaa, 0xe8, 0x07, 0x00, 0xf9, 0xc8, 0x04, 0x00, 0xb4, 0x3a, 0x00, 0x00, 0x94,
			0x60, 0x00, 0x00, 0xb4, 0x88, 0x02, 0x40, 0x39, 0x88, 0x04, 0x00, 0x34, 0x15, 0x00, 0x80, 0x52, 0xe8, 0x07, 0x40,
			0xf9, 0x08, 0x01, 0x00, 0x8b, 0x16, 0x15, 0x40, 0x38, 0xe8, 0x07, 0x00, 0xf9, 0xdf, 0x02, 0x35, 0x6b, 0xc0, 0x02,
			0x00, 0x54, 0xe8, 0x07, 0x40, 0xf9, 0x08, 0x05, 0x00, 0x91, 0x38, 0x00, 0x80, 0x52, 0xf7, 0x03, 0x14, 0xaa, 0x09,
			0xf1, 0x5f, 0x38, 0x49, 0x01, 0x00, 0x34, 0xb8, 0x00, 0x00, 0x36, 0xea, 0x16, 0xc0, 0x38, 0x3f, 0x01, 0x0a, 0x6b,
			0xf8, 0x17, 0x9f, 0x1a, 0x02, 0x00, 0x00, 0x14, 0x18, 0x00, 0x80, 0x52, 0xe8, 0x07, 0x00, 0xf9, 0x08, 0x05, 0x00,
			0x91, 0xf6, 0xff, 0xff, 0x17, 0xe8, 0x07, 0x00, 0xf9, 0x1f, 0x00, 0x00, 0x94, 0xb5, 0x06, 0x00, 0x11, 0x98, 0xfd,
			0x07, 0x36, 0xf4, 0x03, 0x17, 0xaa, 0x02, 0x00, 0x00, 0x14, 0x00, 0x00, 0x80, 0xd2, 0x68, 0x02, 0x00, 0x8b, 0x1f,
			0x00, 0x00, 0xf1, 0xe8, 0x03, 0x88, 0x9a, 0xda, 0xff, 0xff, 0x17, 0x00, 0x00, 0x80, 0xd2, 0x03, 0x00, 0x00, 0x14,
			0x13, 0x00, 0x00, 0x94, 0x12, 0x00, 0x00, 0x94, 0xfd, 0x7b, 0x44, 0xa9, 0xf4, 0x4f, 0x43, 0xa9, 0xf6, 0x57, 0x42,
			0xa9, 0xf8, 0x5f, 0x41, 0xa9, 0xff, 0x43, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6, 0x08, 0x00, 0x40, 0x39, 0x29, 0x00,
			0x40, 0x39, 0x1f, 0x01, 0x09, 0x6b, 0xc1, 0x00, 0x00, 0x54, 0x00, 0x04, 0x00, 0x91, 0x21, 0x04, 0x00, 0x91, 0x48,
			0xff, 0xff, 0x35, 0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6, 0x00, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6,
			0xe0, 0x23, 0x00, 0x91, 0xae, 0xff, 0xff, 0x17, 0x5f, 0x64, 0x6c, 0x6f, 0x70, 0x65, 0x6e, 0x00, 0x5f, 0x64, 0x6c,
			0x73, 0x79, 0x6d, 0x00, 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x5f, 0x5f, 0x4c, 0x49, 0x4e, 0x4b, 0x45, 0x44,
			0x49, 0x54, 0x00, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6c, 0x69, 0x62, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f,
			0x6c, 0x69, 0x62, 0x64, 0x79, 0x6c, 0x64, 0x2e, 0x64, 0x79, 0x6c, 0x69, 0x62, 0x00
		};

		private string demangle (string symbol_name) {
			return (symbol_name[0] == '_')
				? symbol_name.substring (1)
				: symbol_name;
		}

		private class SymbolQueryBuilder {
			private Gee.HashMap<string, Gee.HashSet<string>> symbols = new Gee.HashMap<string, Gee.HashSet<string>> ();

			public unowned SymbolQueryBuilder add (string module_name, string symbol_name) {
				var group = symbols[module_name];
				if (group == null) {
					group = new Gee.HashSet<string> ();
					symbols[module_name] = group;
				}

				group.add (symbol_name);

				return this;
			}

			public SymbolQuery build () {
				var query = new SymbolQuery ();

				var groups = query.groups;
				foreach (var module_entry in symbols.entries) {
					unowned string module_name = module_entry.key;

					var group = new SymbolQueryGroup (module_name);
					groups.add (group);

					var names = group.symbol_names;
					foreach (var symbol_name in module_entry.value)
						names.add (symbol_name);
				}

				return query;
			}
		}

		private class SymbolQuery {
			public Gee.ArrayList<SymbolQueryGroup> groups {
				get;
				private set;
			}

			public SymbolQuery () {
				this.groups = new Gee.ArrayList<SymbolQueryGroup> ();
			}
		}

		private class SymbolQueryGroup {
			public string module_name {
				get;
				private set;
			}

			public Gee.ArrayList<string> symbol_names {
				get;
				private set;
			}

			public SymbolQueryGroup (string module_name) {
				this.module_name = module_name;
				this.symbol_names = new Gee.ArrayList<string> ();
			}
		}

		private class SymbolSet {
			private Gee.HashMap<string, Gee.HashMap<string, uint64?>> symbols =
				new Gee.HashMap<string, Gee.HashMap<string, uint64?>> ();

			public SymbolSet (Gee.HashMap<string, Gee.HashMap<string, uint64?>> symbols) {
				this.symbols = symbols;
			}

			public uint64 get (string module_name, string symbol_name) throws Error {
				uint64 address;
				if (!lookup (module_name, symbol_name, out address))
					throw new Error.UNSUPPORTED ("Symbol not found: %s", symbol_name);
				return address;
			}

			public bool lookup (string module_name, string symbol_name, out uint64 address) {
				address = 0;

				var group = symbols[module_name];
				if (group == null)
					return false;

				uint64? val = group[symbol_name];
				if (val == null)
					return false;

				address = val;
				return true;
			}
		}

		private class ThreadedItemsBuilder {
			private Gee.ArrayList<SymbolReference> symbol_refs = new Gee.ArrayList<SymbolReference> ();
			private Gee.ArrayList<uint64?> region_bases = new Gee.ArrayList<uint64?> ();

			public bool is_empty {
				get {
					return region_bases.is_empty;
				}
			}

			public unowned ThreadedItemsBuilder add_symbol (string module_name, string symbol_name) {
				symbol_refs.add (new SymbolReference (module_name, symbol_name));

				return this;
			}

			public unowned ThreadedItemsBuilder add_region (uint64 base_address) {
				region_bases.add (base_address);

				return this;
			}

			public ThreadedItems build (SymbolSet symbols) throws Error {
				var symbol_addrs = new Gee.ArrayList<uint64?> ();

				var result = new ThreadedItems (symbol_addrs, region_bases);

				foreach (var r in symbol_refs)
					symbol_addrs.add (symbols.get (r.module_name, r.symbol_name));

				return result;
			}

			private class SymbolReference {
				public string module_name;
				public string symbol_name;

				public SymbolReference (string module_name, string symbol_name) {
					this.module_name = module_name;
					this.symbol_name = symbol_name;
				}
			}
		}

		private class ThreadedItems {
			public bool is_empty {
				get {
					return region_bases.is_empty;
				}
			}

			public Gee.ArrayList<uint64?> symbol_addrs {
				get;
				private set;
			}

			public Gee.ArrayList<uint64?> region_bases {
				get;
				private set;
			}

			public ThreadedItems (Gee.ArrayList<uint64?> symbol_addrs, Gee.ArrayList<uint64?> region_bases) {
				this.symbol_addrs = symbol_addrs;
				this.region_bases = region_bases;
			}
		}

		private class ChainedFixupsBuilder {
			private Gee.ArrayList<uint64?> locations = new Gee.ArrayList<uint64?> ();

			public unowned ChainedFixupsBuilder add_location (Gum.Address vm_address) {
				locations.add (vm_address);
				return this;
			}

			public ChainedFixups build () throws Error {
				return new ChainedFixups (locations);
			}
		}

		private class ChainedFixups {
			public Gee.ArrayList<uint64?> locations {
				get;
				private set;
			}

			public ChainedFixups (Gee.ArrayList<uint64?> locations) {
				this.locations = locations;
			}
		}

		private class StringVectorBuilder {
			private LLDB.BufferBuilder buffer_builder;
			private Gee.ArrayList<int> vector = new Gee.ArrayList<int> ();
			private size_t start_offset;

			public uint length {
				get {
					return vector.size;
				}
			}

			public StringVectorBuilder (LLDB.BufferBuilder buffer_builder) {
				this.buffer_builder = buffer_builder;
			}

			public unowned StringVectorBuilder append_string (string val) {
				var offset = buffer_builder.offset;
				buffer_builder.append_string (val);
				vector.add ((int) offset);
				return this;
			}

			public unowned StringVectorBuilder append_terminator () {
				vector.add (-1);
				return this;
			}

			public size_t append_placeholder () {
				start_offset = buffer_builder.offset;

				buffer_builder.skip (vector.size * buffer_builder.pointer_size);

				return start_offset;
			}

			public void build (uint64 address) {
				var vector_offset = start_offset;
				var pointer_size = buffer_builder.pointer_size;

				foreach (int string_offset in vector) {
					uint64 val = (string_offset != -1) ? address + string_offset : 0;
					buffer_builder.write_pointer (vector_offset, val);

					vector_offset += pointer_size;
				}
			}
		}

		private async uint64 invoke_remote_function (uint64 impl, uint64[] args, ExceptionHandler? exception_handler,
				Cancellable? cancellable) throws GLib.Error {
			if (stack_bounds == null) {
				uint64 old_sp = yield main_thread.read_register ("sp", cancellable);
				uint64 our_sp = (old_sp - (old_sp % 16)) - 128;
				stack_bounds = LLDB.Thread.StackBounds (our_sp - (512 * 1024), our_sp);
			}

			yield main_thread.write_register ("pc", impl, cancellable);
			yield main_thread.write_register ("lr", 1337, cancellable);
			yield main_thread.write_register ("sp", stack_bounds.top, cancellable);
			yield main_thread.write_register ("fp", 0, cancellable);

			uint arg_id = 1;
			foreach (uint64 arg_val in args) {
				yield main_thread.write_register ("arg%u".printf (arg_id), arg_val, cancellable);
				arg_id++;
			}

			while (true) {
				var exception = yield lldb.continue_until_exception (cancellable);

				uint64 pc = exception.context["pc"];
				if (pc == 1337)
					break;

				if (exception_handler != null) {
					bool handled = yield exception_handler.try_handle_exception (exception, cancellable);
					if (handled)
						continue;
				}

				throw new Error.UNSUPPORTED ("Invocation of 0x%" + uint64.FORMAT_MODIFIER + "x crashed at %s",
					impl, yield summarize_exception (exception, cancellable));
			}

			return yield main_thread.read_register ("x0", cancellable);
		}

		/*
		private async void dump_lldb_state (Cancellable? cancellable) throws GLib.Error {
			var exception = lldb.exception;
			if (exception != null) {
				var summary = yield summarize_exception (exception, cancellable);
				printerr ("\n# EXCEPTION IN THREAD 0x%x\n\n%s\n", exception.thread.id, summary);
			}

			var threads = new Gee.ArrayList<LLDB.Thread> ();
			yield lldb.enumerate_threads (t => {
				threads.add (t);
				return true;
			}, cancellable);

			var cached_modules = new Gee.ArrayList<LLDB.Module> ();

			printerr ("\nMAIN THREAD: 0x%x\n", main_thread.id);
			printerr ("THREAD COUNT: %u\n", threads.size);

			foreach (var thread in threads) {
				printerr ("\nTHREAD 0x%x:\n", thread.id);

				var pc = yield thread.read_register ("pc", cancellable);
				printerr ("   0x%016" + uint64.FORMAT_MODIFIER + "x\t%s\n",
					pc,
					yield symbolicate_address (pc, cached_modules, cancellable));

				LLDB.Thread.StackBounds? bounds = null;
				if (thread.id == main_thread.id && stack_bounds != null)
					bounds = stack_bounds;

				var frames = yield thread.generate_backtrace (bounds, cancellable);
				foreach (var frame in frames) {
					if (frame.address == 1337)
						break;

					printerr ("   0x%016" + uint64.FORMAT_MODIFIER + "x\t%s\n",
						frame.address,
						yield symbolicate_address (frame.address, cached_modules, cancellable));
				}
			}
		}
		*/

		private async string summarize_exception (LLDB.Exception exception, Cancellable? cancellable) throws GLib.Error {
			var summary = new StringBuilder.sized (256);

			var cached_modules = new Gee.ArrayList<LLDB.Module> ();

			var context = exception.context;
			uint64 pc = context["pc"];
			string pc_symbol = yield symbolicate_address (pc, cached_modules, cancellable);

			summary
				.append (pc_symbol)
				.append (": ")
				.append (exception.to_string ());

			LLDB.Thread.StackBounds? bounds = null;
			if (exception.thread.id == main_thread.id && stack_bounds != null)
				bounds = stack_bounds;

			summary.append_printf ("\n\nLOCATION:\n   0x%016" + uint64.FORMAT_MODIFIER + "x\t%s", pc, pc_symbol);

			var frames = yield exception.thread.generate_backtrace (bounds, cancellable);
			foreach (var frame in frames) {
				if (frame.address == 1337)
					break;

				summary.append_printf ("\n   0x%016" + uint64.FORMAT_MODIFIER + "x\t%s",
					frame.address,
					yield symbolicate_address (frame.address, cached_modules, cancellable));
			}

			return summary.str;
		}

		private async string symbolicate_address (uint64 address, Gee.ArrayList<LLDB.Module> cached_modules,
				Cancellable? cancellable) throws GLib.Error {
			string? description = null;

			if (address >= module.base_address && address < module.base_address + module_size) {
				description = format_module_symbol (module.name, module.base_address, address);
			} else {
				if (cached_modules.is_empty) {
					yield lldb.enumerate_modules (m => {
						cached_modules.add (m);
						return true;
					}, cancellable);
				}

				foreach (var m in cached_modules) {
					var text_segment = m.segments.first_match (s => s.name == "__TEXT");
					if (text_segment != null &&
							address >= m.load_address &&
							address < m.load_address + text_segment.vmsize) {
						description = format_module_symbol (m.pathname, m.load_address, address);
						break;
					}
				}
			}

			if (description == null)
				description = ("0x%" + uint64.FORMAT_MODIFIER + "x").printf (address);

			return description;
		}

		private static string format_module_symbol (string module_name, uint64 module_base, uint64 module_symbol) {
			var tokens = module_name.split ("/");
			unowned string module_basename = tokens[tokens.length - 1];
			uint64 offset = module_symbol - module_base;

			return ("%s!0x%" + uint64.FORMAT_MODIFIER + "x").printf (module_basename, offset);
		}
	}

	private interface ExceptionHandler : Object {
		public abstract async bool try_handle_exception (LLDB.Exception exception, Cancellable? cancellable)
			throws GLib.Error;
	}
}
